1 /** 2 * Dynamic Array 3 * Copyright: © 2015 Economic Modeling Specialists, Intl. 4 * Authors: Brian Schott 5 * License: $(LINK2 http://www.boost.org/LICENSE_1_0.txt, Boost License 1.0) 6 */ 7 8 module containers.dynamicarray; 9 10 private import core.lifetime : move, moveEmplace, copyEmplace, emplace; 11 private import std.traits : isCopyable; 12 private import containers.internal.node : shouldAddGCRange; 13 private import std.experimental.allocator.mallocator : Mallocator; 14 15 /** 16 * Array that is able to grow itself when items are appended to it. Uses 17 * malloc/free/realloc to manage its storage. 18 * 19 * Params: 20 * T = the array element type 21 * Allocator = the allocator to use. Defaults to `Mallocator`. 22 * supportGC = true if the container should support holding references to 23 * GC-allocated memory. 24 */ 25 struct DynamicArray(T, Allocator = Mallocator, bool supportGC = shouldAddGCRange!T) 26 { 27 this(this) @disable; 28 29 private import std.experimental.allocator.common : stateSize; 30 31 static if (is(typeof((T[] a, const T[] b) => a[0 .. b.length] = b[0 .. $]))) 32 { 33 /// Either `const(T)` or `T`. 34 alias AppendT = const(T); 35 36 /// Either `const(typeof(this))` or `typeof(this)`. 37 alias AppendTypeOfThis = const(typeof(this)); 38 } 39 else 40 { 41 alias AppendT = T; 42 alias AppendTypeOfThis = typeof(this); 43 } 44 45 static if (stateSize!Allocator != 0) 46 { 47 /// No default construction if an allocator must be provided. 48 this() @disable; 49 50 /** 51 * Use the given `allocator` for allocations. 52 */ 53 this(Allocator allocator) 54 in 55 { 56 assert(allocator !is null, "Allocator must not be null"); 57 } 58 do 59 { 60 this.allocator = allocator; 61 } 62 } 63 64 ~this() 65 { 66 import std.experimental.allocator.mallocator : Mallocator; 67 import containers.internal.node : shouldAddGCRange; 68 69 if (arr is null) 70 return; 71 72 static if ((is(T == struct) || is(T == union)) 73 && __traits(hasMember, T, "__xdtor")) 74 { 75 foreach (ref item; arr[0 .. l]) 76 { 77 item.__xdtor(); 78 } 79 } 80 static if (useGC) 81 { 82 import core.memory : GC; 83 GC.removeRange(arr.ptr); 84 } 85 allocator.deallocate(arr); 86 } 87 88 /// Slice operator overload 89 pragma(inline, true) 90 auto opSlice(this This)() @nogc 91 { 92 return opSlice!(This)(0, l); 93 } 94 95 /// ditto 96 pragma(inline, true) 97 auto opSlice(this This)(size_t a, size_t b) @nogc 98 { 99 alias ET = ContainerElementType!(This, T); 100 return cast(ET[]) arr[a .. b]; 101 } 102 103 /// Index operator overload 104 pragma(inline, true) 105 ref auto opIndex(this This)(size_t i) @nogc 106 { 107 return opSlice!(This)(i, i + 1)[0]; 108 } 109 110 /** 111 * Inserts the given value into the end of the array. 112 */ 113 void insertBack(T value) 114 { 115 import std.experimental.allocator.mallocator : Mallocator; 116 import containers.internal.node : shouldAddGCRange; 117 118 if (arr.length == 0) 119 { 120 arr = cast(typeof(arr)) allocator.allocate(T.sizeof * 4); 121 static if (useGC) 122 { 123 import core.memory: GC; 124 GC.addRange(arr.ptr, arr.length * T.sizeof); 125 } 126 } 127 else if (l >= arr.length) 128 { 129 immutable size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1; 130 static if (useGC) 131 void* oldPtr = arr.ptr; 132 void[] a = cast(void[]) arr; 133 import std.experimental.allocator.common : reallocate; 134 allocator.reallocate(a, c * T.sizeof); 135 arr = cast(typeof(arr)) a; 136 static if (useGC) 137 { 138 import core.memory: GC; 139 GC.removeRange(oldPtr); 140 GC.addRange(arr.ptr, arr.length * T.sizeof); 141 } 142 } 143 moveEmplace(*cast(ContainerStorageType!T*)&value, arr[l++]); 144 } 145 146 /// ditto 147 alias insert = insertBack; 148 149 /// ditto 150 alias insertAnywhere = insertBack; 151 152 /// ditto 153 alias put = insertBack; 154 155 /** 156 * ~= operator overload 157 */ 158 scope ref typeof(this) opOpAssign(string op)(T value) if (op == "~") 159 { 160 insert(value); 161 return this; 162 } 163 164 /** 165 * ~= operator overload for an array of items 166 */ 167 scope ref typeof(this) opOpAssign(string op, bool checkForOverlap = true)(AppendT[] rhs) 168 if (op == "~" && !is(T == AppendT[])) 169 { 170 // Disabling checkForOverlap when this function is called from opBinary!"~" 171 // is not just for efficiency, but to avoid circular function calls that 172 // would prevent inference of @nogc, etc. 173 static if (checkForOverlap) 174 if ((() @trusted => arr.ptr <= rhs.ptr && arr.ptr + arr.length > rhs.ptr)()) 175 { 176 // Special case where rhs is a slice of this array. 177 this = this ~ rhs; 178 return this; 179 } 180 reserve(l + rhs.length); 181 import std.traits: hasElaborateAssign, hasElaborateDestructor; 182 static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) 183 { 184 foreach (ref value; rhs) 185 copyEmplace(value, arr[l++]); 186 } 187 else 188 { 189 arr[l .. l + rhs.length] = rhs[0 .. rhs.length]; 190 l += rhs.length; 191 } 192 return this; 193 } 194 195 /// ditto 196 scope ref typeof(this) opOpAssign(string op)(ref AppendTypeOfThis rhs) 197 if (op == "~") 198 { 199 return this ~= rhs.arr[0 .. rhs.l]; 200 } 201 202 /** 203 * ~ operator overload 204 */ 205 typeof(this) opBinary(string op)(ref AppendTypeOfThis other) if (op == "~") 206 { 207 typeof(this) ret; 208 ret.reserve(l + other.l); 209 ret.opOpAssign!("~", false)(arr[0 .. l]); 210 ret.opOpAssign!("~", false)(other.arr[0 .. other.l]); 211 return ret; 212 } 213 214 /// ditto 215 typeof(this) opBinary(string op)(AppendT[] values) if (op == "~") 216 { 217 typeof(this) ret; 218 ret.reserve(l + values.length); 219 ret.opOpAssign!("~", false)(arr[0 .. l]); 220 ret.opOpAssign!("~", false)(values); 221 return ret; 222 } 223 224 /** 225 * Ensures sufficient capacity to accommodate `n` elements. 226 */ 227 void reserve(size_t n) 228 { 229 if (arr.length >= n) 230 return; 231 if (arr.ptr is null) 232 { 233 size_t c = 4; 234 if (c < n) 235 c = n; 236 arr = cast(typeof(arr)) allocator.allocate(T.sizeof * c); 237 static if (useGC) 238 { 239 import core.memory: GC; 240 GC.addRange(arr.ptr, arr.length * T.sizeof); 241 } 242 } 243 else 244 { 245 size_t c = arr.length > 512 ? arr.length + 1024 : arr.length << 1; 246 if (c < n) 247 c = n; 248 static if (useGC) 249 void* oldPtr = arr.ptr; 250 void[] a = cast(void[]) arr; 251 import std.experimental.allocator.common : reallocate; 252 allocator.reallocate(a, c * T.sizeof); 253 arr = cast(typeof(arr)) a; 254 static if (useGC) 255 { 256 import core.memory: GC; 257 GC.removeRange(oldPtr); 258 GC.addRange(arr.ptr, arr.length * T.sizeof); 259 } 260 } 261 } 262 263 /** 264 * Change the array length. 265 * When growing, initialize new elements to the default value. 266 */ 267 static if (is(typeof({static T value;}))) // default construction is allowed 268 void resize(size_t n) 269 { 270 import std.traits: hasElaborateAssign, hasElaborateDestructor; 271 auto toFill = resizeStorage(n); 272 static if (is(T == struct) && hasElaborateDestructor!T) 273 { 274 foreach (ref target; toFill) 275 emplace(&target); 276 } 277 else 278 toFill[] = T.init; 279 } 280 281 /** 282 * Change the array length. 283 * When growing, initialize new elements to the given value. 284 */ 285 static if (isCopyable!T) 286 void resize(size_t n, T value) 287 { 288 import std.traits: hasElaborateAssign, hasElaborateDestructor; 289 auto toFill = resizeStorage(n); 290 static if (is(T == struct) && (hasElaborateAssign!T || hasElaborateDestructor!T)) 291 { 292 foreach (ref target; toFill) 293 copyEmplace(value, target); 294 } 295 else 296 toFill[] = value; 297 } 298 299 // Resizes storage only, and returns slice of new memory to fill. 300 private ContainerStorageType!T[] resizeStorage(size_t n) 301 { 302 ContainerStorageType!T[] toFill = null; 303 304 if (arr.length < n) 305 reserve(n); 306 307 if (l < n) // Growing? 308 { 309 toFill = arr[l..n]; 310 } 311 else 312 { 313 static if ((is(T == struct) || is(T == union)) 314 && __traits(hasMember, T, "__xdtor")) 315 { 316 foreach (i; n..l) 317 arr[i].__xdtor(); 318 } 319 } 320 321 l = n; 322 return toFill; 323 } 324 325 /** 326 * Remove the item at the given index from the array. 327 */ 328 void remove(const size_t i) 329 { 330 if (i < this.l) 331 { 332 auto next = i + 1; 333 while (next < this.l) 334 { 335 move(arr[next], arr[next - 1]); 336 ++next; 337 } 338 339 --l; 340 static if ((is(T == struct) || is(T == union)) 341 && __traits(hasMember, T, "__xdtor")) 342 { 343 arr[l].__xdtor(); 344 } 345 } 346 else 347 { 348 import core.exception : RangeError; 349 throw new RangeError("Out of range index used to remove element"); 350 } 351 } 352 353 /** 354 * Removes the last element from the array. 355 */ 356 void removeBack() 357 { 358 this.remove(this.length - 1); 359 } 360 361 /// Index assignment support 362 void opIndexAssign(T value, size_t i) @nogc 363 { 364 arr[i] = move(*cast(ContainerStorageType!T*)&value); 365 } 366 367 /// Slice assignment support 368 static if (isCopyable!T) 369 void opSliceAssign(T value) @nogc 370 { 371 arr[0 .. l] = value; 372 } 373 374 /// ditto 375 static if (isCopyable!T) 376 void opSliceAssign(T value, size_t i, size_t j) @nogc 377 { 378 arr[i .. j] = value; 379 } 380 381 /// ditto 382 static if (isCopyable!T) 383 void opSliceAssign(T[] values) @nogc 384 { 385 arr[0 .. l] = values[]; 386 } 387 388 /// ditto 389 static if (isCopyable!T) 390 void opSliceAssign(T[] values, size_t i, size_t j) @nogc 391 { 392 arr[i .. j] = values[]; 393 } 394 395 /// Returns: the number of items in the array 396 size_t length() const nothrow pure @property @safe @nogc { return l; } 397 398 /// Ditto 399 alias opDollar = length; 400 401 /// Returns: whether or not the DynamicArray is empty. 402 bool empty() const nothrow pure @property @safe @nogc { return l == 0; } 403 404 /** 405 * Returns: a slice to the underlying array. 406 * 407 * As the memory of the array may be freed, access to this array is 408 * highly unsafe. 409 */ 410 auto ptr(this This)() @nogc @property 411 { 412 alias ET = ContainerElementType!(This, T); 413 return cast(ET*) arr.ptr; 414 } 415 416 /// Returns: the front element of the DynamicArray. 417 auto ref T front() pure @property 418 { 419 return arr[0]; 420 } 421 422 /// Returns: the back element of the DynamicArray. 423 auto ref T back() pure @property 424 { 425 return arr[l - 1]; 426 } 427 428 private: 429 430 import containers.internal.storage_type : ContainerStorageType; 431 import containers.internal.element_type : ContainerElementType; 432 import containers.internal.mixins : AllocatorState; 433 434 enum bool useGC = supportGC && shouldAddGCRange!T; 435 mixin AllocatorState!Allocator; 436 ContainerStorageType!(T)[] arr; 437 size_t l; 438 } 439 440 version(emsi_containers_unittest) unittest 441 { 442 import std.algorithm : equal; 443 import std.range : iota; 444 DynamicArray!int ints; 445 assert(ints.empty); 446 foreach (i; 0 .. 100) 447 { 448 ints.insert(i); 449 assert(ints.front == 0); 450 assert(ints.back == i); 451 } 452 453 assert (equal(ints[], iota(100))); 454 assert (ints.length == 100); 455 ints[0] = 100; 456 assert (ints[0] == 100); 457 ints[0 .. 5] = 20; 458 foreach (i; ints[0 .. 5]) 459 assert (i == 20); 460 ints[] = 432; 461 foreach (i; ints[]) 462 assert (i == 432); 463 464 auto arr = ints.ptr; 465 arr[0] = 1337; 466 assert(arr[0] == 1337); 467 assert(ints[0] == 1337); 468 } 469 470 version(emsi_containers_unittest) 471 { 472 class Cls 473 { 474 int* a; 475 476 this(int* a) 477 { 478 this.a = a; 479 } 480 481 ~this() 482 { 483 ++(*a); 484 } 485 } 486 } 487 488 version(emsi_containers_unittest) unittest 489 { 490 int* a = new int; 491 { 492 DynamicArray!(Cls) arr; 493 arr.insert(new Cls(a)); 494 } 495 assert(*a == 0); // Destructor not called. 496 } 497 498 version(emsi_containers_unittest) unittest 499 { 500 import std.exception : assertThrown; 501 import core.exception : RangeError; 502 DynamicArray!int empty; 503 assertThrown!RangeError(empty.remove(1337)); 504 assert(empty.length == 0); 505 506 DynamicArray!int one; 507 one.insert(0); 508 assert(one.length == 1); 509 assertThrown!RangeError(one.remove(1337)); 510 assert(one.length == 1); 511 one.remove(0); 512 assert(one.length == 0); 513 514 DynamicArray!int two; 515 two.insert(0); 516 two.insert(1); 517 assert(two.length == 2); 518 assertThrown!RangeError(two.remove(1337)); 519 assert(two.length == 2); 520 two.remove(0); 521 assert(two.length == 1); 522 assert(two[0] == 1); 523 two.remove(0); 524 assert(two.length == 0); 525 526 two.insert(0); 527 two.insert(1); 528 assert(two.length == 2); 529 530 two.remove(1); 531 assert(two.length == 1); 532 assert(two[0] == 0); 533 assertThrown!RangeError(two.remove(1)); 534 assert(two.length == 1); 535 assert(two[0] == 0); 536 two.remove(0); 537 assert(two.length == 0); 538 } 539 540 version(emsi_containers_unittest) unittest 541 { 542 int* a = new int; 543 DynamicArray!(Cls, Mallocator, true) arr; 544 arr.insert(new Cls(a)); 545 546 arr.remove(0); 547 assert(*a == 0); // Destructor not called. 548 } 549 550 version(emsi_containers_unittest) unittest 551 { 552 DynamicArray!(int*, Mallocator, true) arr; 553 554 foreach (i; 0 .. 4) 555 arr.insert(new int(i)); 556 557 assert (arr.length == 4); 558 559 int*[] slice = arr[1 .. $ - 1]; 560 assert (slice.length == 2); 561 assert (*slice[0] == 1); 562 assert (*slice[1] == 2); 563 } 564 565 version(emsi_containers_unittest) unittest 566 { 567 import std.format : format; 568 569 DynamicArray!int arr; 570 foreach (int i; 0 .. 10) 571 arr ~= i; 572 assert(arr.length == 10, "arr.length = %d".format(arr.length)); 573 574 auto arr2 = arr ~ arr; 575 assert(arr2.length == 20); 576 auto arr3 = arr2 ~ [100, 99, 98]; 577 assert(arr3.length == 23); 578 579 while(!arr3.empty) 580 arr3.removeBack(); 581 assert(arr3.empty); 582 583 } 584 585 version(emsi_containers_unittest) @system unittest 586 { 587 DynamicArray!int a; 588 a.reserve(1000); 589 assert(a.length == 0); 590 assert(a.empty); 591 assert(a.arr.length >= 1000); 592 int* p = a[].ptr; 593 foreach (i; 0 .. 1000) 594 { 595 a.insert(i); 596 } 597 assert(p is a[].ptr); 598 } 599 600 version(emsi_containers_unittest) unittest 601 { 602 // Ensure that Array.insert doesn't call the destructor for 603 // a struct whose state is uninitialized memory. 604 static struct S 605 { 606 int* a; 607 ~this() @nogc nothrow 608 { 609 if (a !is null) 610 ++(*a); 611 } 612 } 613 int* a = new int; 614 { 615 DynamicArray!S arr; 616 // This next line may segfault if destructors are called 617 // on structs in invalid states. 618 arr.insert(S(a)); 619 } 620 assert(*a == 1); 621 } 622 623 version(emsi_containers_unittest) @nogc unittest 624 { 625 struct HStorage 626 { 627 import containers.dynamicarray: DynamicArray; 628 DynamicArray!int storage; 629 } 630 auto hs = HStorage(); 631 } 632 633 version(emsi_containers_unittest) @nogc unittest 634 { 635 DynamicArray!char a; 636 const DynamicArray!char b = a ~ "def"; 637 a ~= "abc"; 638 a ~= b; 639 assert(a[] == "abcdef"); 640 a ~= a; 641 assert(a[] == "abcdefabcdef"); 642 } 643 644 version(emsi_containers_unittest) unittest 645 { 646 enum initialValue = 0x69FF5705DAD1AB6CUL; 647 enum payloadValue = 0x495343303356D18CUL; 648 649 static struct S 650 { 651 ulong value = initialValue; 652 @nogc: 653 @disable this(); 654 this(ulong value) { this.value = value; } 655 ~this() { assert(value == initialValue || value == payloadValue); } 656 } 657 658 auto s = S(payloadValue); 659 660 DynamicArray!S arr; 661 arr.insertBack(s); 662 arr ~= [s]; 663 } 664 665 version(emsi_containers_unittest) @nogc unittest 666 { 667 DynamicArray!int a; 668 a.resize(5, 42); 669 assert(a.length == 5); 670 assert(a[2] == 42); 671 a.resize(3, 17); 672 assert(a.length == 3); 673 assert(a[2] == 42); 674 675 struct Counter 676 { 677 @nogc: 678 static int count; 679 @disable this(); 680 this(int) { count++; } 681 this(this) { count++; } 682 ~this() { count--; } 683 } 684 685 DynamicArray!Counter b; 686 assert(Counter.count == 0); 687 static assert(!is(typeof(b.resize(5)))); 688 b.resize(5, Counter(0)); 689 assert(Counter.count == 5); 690 b.resize(3, Counter(0)); 691 assert(Counter.count == 3); 692 } 693 694 version(emsi_containers_unittest) @nogc unittest 695 { 696 struct S { int i = 42; @disable this(this); } 697 DynamicArray!S a; 698 a.resize(1); 699 assert(a[0].i == 42); 700 } 701 702 version(emsi_containers_unittest) unittest 703 { 704 import std.experimental.allocator.building_blocks.region : Region; 705 auto region = Region!Mallocator(1024); 706 707 auto arr = DynamicArray!(int, Region!(Mallocator)*, true)(®ion); 708 // reserve and insert back call the common form of reallocate 709 arr.reserve(10); 710 arr.insertBack(1); 711 assert(arr[0] == 1); 712 } 713 714 version(emsi_containers_unittest) unittest 715 { 716 auto arr = DynamicArray!int(); 717 arr.resize(5); 718 arr[] = [1, 2, 3, 4, 5]; 719 arr[1 .. 4] = [12, 13, 14]; 720 assert(arr[] == [1, 12, 13, 14, 5]); 721 }